package network.protocol;
import java.util.TreeMap;
import java.util.Vector;

import network.NetworkConstants;
import network.byteConversions.ByteConverter;
import network.dataFormats.NetworkData;
import network.dataFormats.NetworkPackage;
import network.dataFormats.PackageType;
/**
 * This class is used to send large network packages (like INIT packages that contain the whole game class in one single package) across the network by splitting them into smaller pieces and joining them back together on the other side.
 * 
 * @author toni
 * 
 */
public class PackageSplitter {
	// The allowed size is calculated by the maximum size that can be sent with
	// the socket minus the header bytes that will be added (NetworkPackages are
	// incapsulated in NetworkData and SocketHeader types)
	private static final int SPLITPACKAGEHEADERSIZE = 12;
	public static final int ALLOWEDSIZE = NetworkConstants.PACKAGESIZE - NetworkData.HEADERSIZE - SPLITPACKAGEHEADERSIZE;
	private TreeMap<Integer, Vector<NetworkPackage>> idsToPackages;
	public PackageSplitter() {
		idsToPackages = new TreeMap<Integer, Vector<NetworkPackage>>();
	}
	/**
	 * Takes a list of network packages, checks their size and splits them if needed, does not change the order of the packages.
	 * 
	 * @author toni
	 * 
	 * @param packages
	 * @return
	 */
	public Vector<NetworkPackage> splitBigPackages(Vector<NetworkPackage> packages) {
		Vector<NetworkPackage> result = new Vector<NetworkPackage>();
		for (NetworkPackage p : packages) {
			if (p.getSize() > ALLOWEDSIZE) {
				Vector<NetworkPackage> splitted = split(p);
				for (NetworkPackage small : splitted) {
					result.add(small);
				}
			}
			else {
				result.add(p);
			}
		}
		return result;
	}
	/**
	 * Splits a single package
	 * 
	 * @author toni
	 * 
	 * @param p
	 *            : The package to be split.
	 * @return
	 */
	private Vector<NetworkPackage> split(NetworkPackage p) {
		int newPackageSize = ALLOWEDSIZE - SPLITPACKAGEHEADERSIZE;
		Vector<NetworkPackage> packages = new Vector<NetworkPackage>();
		int numPackages = (int) Math.ceil((float) p.getSize() / newPackageSize);
		int start = 0, end;
		for (int i = 0; i < numPackages; i++) {
			end = Math.min(start + newPackageSize, p.bytes.length);
			byte[] data = new byte[end - start + SPLITPACKAGEHEADERSIZE];
			ByteConverter.intToFourBytes(i, data, 0);
			ByteConverter.intToFourBytes(p.header.id, data, 4);
			ByteConverter.intToFourBytes(numPackages, data, 8);
			for (int j = 0; j < data.length - SPLITPACKAGEHEADERSIZE; j++) {
				data[j + SPLITPACKAGEHEADERSIZE] = p.bytes[j + start];
			}
			start = end;
			NetworkPackage pasd = new NetworkPackage(PackageType.SPLIT, data, -1, 0.0f);
			packages.add(pasd);
		}
		return packages;
	}
	/**
	 * Takes all packages that arrived, sorts the non-split ones out (adds them to the result list), saves the split ones in a treemap. Whenever all packages of one id have arrived they will be joined to one package and added to the resulting list.
	 * 
	 * @author toni
	 * 
	 * @param packages
	 * @return
	 */
	public Vector<NetworkPackage> joinSplitPackages(Vector<NetworkPackage> packages) {
		Vector<NetworkPackage> packagesJoined = new Vector<NetworkPackage>();
		for (NetworkPackage p : packages) {
			if (p.header.type == PackageType.SPLIT) {
				int id = getId(p);
				int size = getSize(p);
				if (!idsToPackages.containsKey(id)) {
					idsToPackages.put(id, new Vector<NetworkPackage>());
				}
				idsToPackages.get(id).add(p);
				Vector<NetworkPackage> k = idsToPackages.get(id);
				if (k.size() == size) { // All packages arrived
					NetworkPackage resulting = join(k);
					packagesJoined.add(resulting);
					idsToPackages.remove(id);
				}
			}
			else {
				packagesJoined.add(p);
			}
		}
		return packagesJoined;
	}
	private NetworkPackage join(Vector<NetworkPackage> k) {
		Vector<NetworkPackage> sorted = sortPackages(k);
		int sumSize = 0;
		for (NetworkPackage p : sorted) {
			sumSize += p.bytes.length - SPLITPACKAGEHEADERSIZE - NetworkPackage.HEADERSIZE;
		}
		byte[] result = new byte[sumSize];
		int start = 0;
		for (NetworkPackage p : sorted) {
			int size = p.bytes.length - SPLITPACKAGEHEADERSIZE - NetworkPackage.HEADERSIZE;
			System.arraycopy(p.bytes, SPLITPACKAGEHEADERSIZE + NetworkPackage.HEADERSIZE, result, start, size);
			start = start + size;
		}
		return new NetworkPackage(result);
	}
	private Vector<NetworkPackage> sortPackages(Vector<NetworkPackage> k) {
		Vector<NetworkPackage> sorted = new Vector<NetworkPackage>();
		int numPackages = k.size();
		// TODO
		assert (numPackages <= 200); // Otherwise the following "algorithm" will
										// suck. (Actually it even sucks when
										// the list is smaller but I don't have
										// any more time. This is just to be
										// sure this won't slow down the program
										// significantly later)
		for (int j = 0; j < numPackages; j++) {
			int minI = -1;
			int minIndex = numPackages + 1;
			for (int i = 0; i < k.size(); i++) {
				int index = getIndex(k.get(i));
				if (index < minIndex) {
					minIndex = index;
					minI = i;
				}
			}
			sorted.add(k.get(minI));
			k.remove(minI);
		}
		return sorted;
	}
	public static int getIndex(NetworkPackage p) {
		// The index is in bytes 0-3
		return ByteConverter.bytesToInt(p.bytes, NetworkPackage.HEADERSIZE);
	}
	public static int getId(NetworkPackage p) {
		// The id is in bytes 4-7
		return ByteConverter.bytesToInt(p.bytes, NetworkPackage.HEADERSIZE + 4);
	}
	public static int getSize(NetworkPackage p) {
		// The size is in bytes 8-11
		return ByteConverter.bytesToInt(p.bytes, NetworkPackage.HEADERSIZE + 8);
	}
	public void clear() {
		idsToPackages.clear();
	}
}
